package sengine;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Detect when sprites collide
 *
 * @author twoodf5611
 */
public final class CollisionDetector {

    private final ArrayList<Object2D> xstart = new ArrayList<>();
    private final ArrayList<Object2D> xend = new ArrayList<>();
    private final ArrayList<Object2D> ystart = new ArrayList<>();
    private final ArrayList<Object2D> yend = new ArrayList<>();
    private final ArrayList<Collision> collisions = new ArrayList<>();

    /**
     * Add a new Object2D to be tracked. Note that results will be inconsistent
     * until update() is called next.
     *
     * @param sprite The Object2D to add
     */
    public void addObject(Object2D sprite) {
        xstart.add(sprite);
        ystart.add(sprite);
        xend.add(sprite);
        yend.add(sprite);
    }

    /**
     * Remove a sprite from the tracking lists. Note that results will be
     * inconsistent until update() is called next.
     *
     * @param sprite The sprite to remove
     */
    public void removeObject(Object2D sprite) {
        xstart.remove(sprite);
        ystart.remove(sprite);
        xend.remove(sprite);
        yend.remove(sprite);
    }

    /**
     * Get the current list of collisions.
     *
     * @return The current collisions list
     */
    public List<Collision> getCollisions() {
        return collisions;
    }

    public void update() {
        // Sort everything based on the new position values
        Collections.sort(xstart, xsSortComp);
        Collections.sort(ystart, ysSortComp);
        Collections.sort(xend, xeSortComp);
        Collections.sort(yend, yeSortComp);

       // Check overlaps
        ArrayList<Collision> overlaps = new ArrayList<>(); // Make this a HashSet?
        for (int xs = 0; xs < xstart.size(); xs++) {
            Object2D xsO = xstart.get(xs);
            //DBG System.out.println("xsO="+xsO);
            for (int xe = 0; xe < xend.size(); xe++) {
                Object2D xeO = xend.get(xe);
                //if ((xsO.getX() + xsO.getWidth()) < xeO.getX()) {
                //    break;
                //}
                //DBG System.out.println("us="+xsO.getX());
                //DBG System.out.println("ue="+xeO.getX());
                if (xsO != xeO && (xsO.getX() + xsO.getWidth()) >= xeO.getX() && xsO.getX() < (xeO.getX() + xeO.getWidth())) {
                    //DBG System.out.println("s="+(xsO.getX()+xsO.getWidth()));
                    //DBG System.out.println("e="+xeO.getX());
                    // We have an overlap in the X direction
                    Collision newCol = new Collision(xsO, xeO);
                    float base = xsO.getX() - xeO.getX();
                    if (Math.abs(base + xsO.getWidth()) < 2) {
                        newCol.setXdirection(Collision.NEGATIVE);
                    } else if (Math.abs(xeO.getWidth() - base) < 2) {
                        newCol.setXdirection(Collision.POSITIVE);
                        //DBG System.out.println(newCol.getXdirection());
                    } else {
                        newCol.setXdirection(Collision.NONE);
                    }
                    if (!overlaps.contains(newCol)) {
                        overlaps.add(newCol);
                    }
                }
            }
        }
        //DBG System.out.println(overlaps);

        // Clear previous collisions
        collisions.clear();

        // Do y-axis detection
        for (int ys = 0; ys < ystart.size(); ys++) {
            Object2D ysO = ystart.get(ys);
            for (int ye = 0; ye < xend.size(); ye++) {
                Object2D yeO = yend.get(ye);
                //if (ysO.getY() > (yeO.getY() + yeO.getHeight())) {
                //    break;
                //}
                if (ysO != yeO && (ysO.getY() + ysO.getHeight()) >= yeO.getY() && ysO.getY() < (yeO.getY() + yeO.getHeight())) {
                    int previndex;
                    if ((previndex = overlaps.indexOf(new Collision(ysO, yeO))) != -1) {
                        Collision nextCol = overlaps.get(previndex);
                        float base = ysO.getY() - yeO.getY();
                        if (Math.abs(base) < 2) {
                            nextCol.setYdirection(Collision.NEGATIVE);
                        } else if (Math.abs(base + ysO.getHeight()) < 2) {
                            nextCol.setYdirection(Collision.POSITIVE);
                        } else {
                            nextCol.setYdirection(Collision.NONE);
                        }
                        collisions.add(nextCol);
                    }
                }
            }
        }

        // Trim
        List<Collision> discard = new ArrayList<>();
        for (Collision col1 : collisions) {
            for (Collision col2 : collisions) {
                if (col1 == col2) {
                    continue;
                }
                if (col1.getObj1() == col2.getObj2() && col1.getObj1() == col2.getObj2()) {
                    if (discard.contains(col1) || discard.contains(col2)) {
                        continue;
                    }
                    //DBG System.out.println("Collision collision: "+col1+", "+col2);
                    if (col1.getXdirection() == 0 && col1.getYdirection() == 0) {
                        discard.add(col1);
                    } else if (col2.getXdirection() == 0 && col2.getYdirection() == 0) {
                        discard.add(col2);
                    }
                }
            }
        }
        for (Collision disc : discard) {
            collisions.remove(disc);
        }
    }

    /**
     * Like binary search, but returns the first object with at least a certain
     * value. Hopefully, it helps performance.
     */
    private int binaryMinFudge(ArrayList<Object2D> sprites, Comparator<Object2D> comp, Object2D target) {
        int low = 0;
        int high = sprites.size() - 1;

        if (high <= low) {
            return 0; // Only 1 element
        }

        int min = high;

        while (low < high) {
            int mid = (high - low) >>> 1; // I love cheating (this means divide by 2, but it's faster)
            int cmp = comp.compare(sprites.get(mid), target);
            if (cmp < 0) {
                low = mid + 1;
            } else if (cmp > 0) {
                min = mid;
                high = mid - 1;
            } else {
                return mid; // We have an exact match
            }
        }
        return min;
    }
    // Create every comparator only once to speed up update()
    protected static Comparator<Object2D> xsSortComp = new Comparator<Object2D>() {
        @Override
        public int compare(Object2D s1, Object2D s2) {
            return (int) (s1.getX() - s2.getX()); //WARNING: This might not work at very close distances
        }
    };
    protected static Comparator<Object2D> ysSortComp = new Comparator<Object2D>() {
        @Override
        public int compare(Object2D s1, Object2D s2) {
            return (int) (s1.getY() - s2.getY()); //WARNING: This might not work at very close distances
        }
    };
    protected static Comparator<Object2D> xeSortComp = new Comparator<Object2D>() {
        @Override
        public int compare(Object2D s1, Object2D s2) {
            return (int) ((s1.getX() + s1.getWidth()) - (s2.getX() + s2.getWidth())); //WARNING: This might not work at very close distances
        }
    };
    protected static Comparator<Object2D> yeSortComp = new Comparator<Object2D>() {
        @Override
        public int compare(Object2D s1, Object2D s2) {
            return (int) ((s1.getY() + s1.getHeight()) - (s2.getY() + s2.getHeight())); //WARNING: This might not work at very close distances
        }
    };
    protected static Comparator<Object2D> xOverlapComp = new Comparator<Object2D>() {
        @Override
        public int compare(Object2D s1, Object2D s2) {
            return (int) ((s1.getX() + s1.getWidth()) - s2.getX()); //WARNING: This might not work at very close distances
        }
    };
}
